home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Users Group Library 1996 July / C-C++ Users Group Library July 1996.iso / vol_400 / 408_01 / snews.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-08-06  |  42.5 KB  |  1,565 lines

  1. /*
  2.     SNEWS 1.91
  3.  
  4.     snews - a simple threaded news reader
  5.  
  6.  
  7.     Copyright (C) 1991  John McCombs, Christchurch, NEW ZEALAND
  8.                         john@ahuriri.gen.nz
  9.                         PO Box 2708, Christchurch, NEW ZEALAND
  10.  
  11.     Modifications copyright (C) 1993  Daniel Fandrich
  12.                         <dan@fch.wimsey.bc.ca> or CompuServe 72365,306
  13.  
  14.     This program is free software; you can redistribute it and/or modify
  15.     it under the terms of the GNU General Public License, version 1, as
  16.     published by the Free Software Foundation.
  17.  
  18.     This program is distributed in the hope that it will be useful,
  19.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  20.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21.     GNU General Public License for more details.
  22.  
  23.     See the file COPYING, which contains a copy of the GNU General
  24.     Public License.
  25.  
  26.  
  27.     Source is formatted with a tab size of 4.
  28.  */
  29.  
  30. #include "defs.h"
  31. #include "snews.h"
  32. #include "pccharst.h"
  33. #include "getopt.h"
  34. #include <alloc.h>
  35. #include <ctype.h>
  36. #include <process.h>
  37.  
  38. unsigned _stklen = 8192;        /* default 4k stack can give problems */
  39.  
  40. INFO my_stuff;
  41.  
  42. /*------------------------------- main --------------------------------*/
  43. int main(int argc, char *argv[])
  44. {
  45.     ACTIVE *gp, *head;
  46.     int done, option;
  47.     int verbose = 1, errflag = 0;
  48.  
  49.     signal(SIGINT, sig_break);        /* turn control-break off */
  50.  
  51.     if (getenv("BIOSVIDEO") ||
  52.        (getenv("TERM") && (stricmp(getenv("TERM"), "pcbios") == 0)))
  53.         directvideo = 0;
  54.  
  55.     select_code_page();
  56.  
  57.     while ((option = getopt(argc, argv, "bqv?")) != -1)
  58.         switch (option) {
  59.             case 'b':        /* bios video output */
  60.                 directvideo = 0;
  61.                 break;
  62.  
  63.             case 'q':        /* quiet mode */
  64.                 verbose = 0;
  65.                 break;
  66.  
  67.             case 'v':        /* verbose mode */
  68.                 ++verbose;
  69.                 break;
  70.  
  71.             case '?':        /* display help */
  72.                 ++errflag;
  73.                 break;
  74.         };
  75.  
  76.     if ((argc > 1) && (argv[1][0] == '/'))
  77.         ++errflag;
  78.  
  79.     if (verbose)
  80.         printf(VERSION "\n"
  81.             "type snews -? for usage\n\n");
  82.     if ((verbose >= 2) || errflag)
  83.         printf(
  84.          "Copyright (C) 1991  John McCombs\n"
  85.          "Copyright (C) 1992  Michael Studte/John Dennis (Shinohara Industries)\n"
  86.          "Copyright (C) 1992  Kai Uwe Rommel\n"
  87.          "Copyright (C) 1993  Daniel Fandrich\n\n"
  88.  
  89.          "SNEWS comes with ABSOLUTELY NO WARRANTY.\n"
  90.          "This is free software, and you are welcome to redistribute it\n"
  91.          "under certain conditions; see the file COPYING for details.\n\n");
  92.  
  93.     if (errflag) {
  94.         printf(
  95.             "usage: snews [-q] [-v] [-b]\n"
  96.             "  -q  set quiet mode\n"
  97.             "  -v  set verbose mode\n"
  98.             "  -b  use BIOS for video output\n"
  99.         );
  100.         return 2;
  101.     }
  102.  
  103.     if (verbose)
  104.         printf("loading config... ");
  105.     if (load_stuff()) {
  106.  
  107.         if (verbose)
  108.             printf("loading active... ");
  109.         head = load_active_file();
  110.         if (verbose)
  111.             printf("loading read list... ");
  112.         load_read_list();
  113.         if (verbose)
  114.             printf("loading history... ");
  115.         load_history_list();
  116.  
  117.         done = FALSE;
  118.         gp = NULL;
  119.  
  120.         while (!done) {
  121.             if ((gp = select_group(head, gp)) != NULL) {
  122.                 done |= read_group(gp);
  123.             } else {
  124.                 done = TRUE;
  125.             }
  126.         }
  127.  
  128.         clrscr();
  129.  
  130.         free_hist_list();
  131.         if (verbose)
  132.             printf("writing read list... ");
  133.         save_read_list();
  134.         if (verbose)
  135.             printf("done\n");
  136.         close_active_file();
  137.     } else {
  138.         fprintf(stderr, "Couldn't find neccessary item in the .rc files\n");
  139.         return 2;
  140.     }
  141.     return 0;
  142. }
  143.  
  144.  
  145.  
  146.  
  147. /*-------------------------- find which group to read -----------------------*/
  148. ACTIVE *select_group(ACTIVE *head, ACTIVE *current)
  149. {
  150.     /*
  151.      *  Present the list of groups, and allow him to move up and down with
  152.      *  the arrow and PgUp and PgDn keys.  'h' for help, -/+ for searches
  153.      */
  154.  
  155.     ACTIVE *top;        /* newsgroup at the top of the page */
  156.     ACTIVE *this;       /* current newsgroup                */
  157.     ACTIVE *tmp_ng;
  158.     enum exit_codes exit_code;   /* why we are exiting the loop      */
  159.     char   sub_tmp[80];
  160.  
  161.     int    i, j, articles, unread;
  162.  
  163.     this = (current == NULL) ? head : current;
  164.  
  165.     top = head;
  166.     exit_code = EX_CONT;
  167.  
  168.     show_groups(&top, this, TRUE);
  169.  
  170.     while (exit_code == EX_CONT) {
  171.  
  172.         switch (get_any_key()) {
  173.  
  174.                     case Fn1    :
  175.                     case '?'    :
  176.                     case 'h'    :
  177.                         show_help(HELP_GROUP);
  178.                         show_groups(&top, this, TRUE);
  179.                         break;
  180.  
  181.                     case Fn2    :
  182.                         show_values();
  183.                         show_groups(&top, this, TRUE);
  184.                         break;
  185.  
  186.                     case UP_ARR :
  187.                         if (this->last != NULL) this = this->last;
  188.                         break;
  189.  
  190.                     case DN_ARR :
  191.                         if (this->next != NULL) this = this->next;
  192.                         break;
  193.  
  194.                     case PGUP   :
  195.                         for (i = 0; i < PAGE_LENGTH; i++) {
  196.                             if (this->last == NULL) break;
  197.                             this = this->last;
  198.                         }
  199.                         break;
  200.  
  201.                     case PGDN   :
  202.                         for (i = 0; i < PAGE_LENGTH; i++) {
  203.                             if (this->next == NULL) break;
  204.                             this = this->next;
  205.                         }
  206.                         break;
  207.  
  208.                     case '1'    :
  209.                     case HOME   :
  210.                         top = this = head;
  211.                         show_groups(&top, this, TRUE);
  212.                         break;
  213.  
  214.                     case '$'    :
  215.                     case END    :
  216.                         this = head;
  217.                         while (this->next != NULL)
  218.                             this = this->next;
  219.                         show_groups(&top, this, TRUE);
  220.                         break;
  221.  
  222.             case '/'    :
  223.             case '+'    :
  224.                 this = search_groups(this);
  225.                 break;
  226.  
  227.             case 'p'    :
  228.                 strcpy(sub_tmp, "");
  229.                 post(NULL, this->group, sub_tmp);
  230.                 show_groups(&top, this, TRUE);
  231.                 break;
  232.  
  233.             case 'c'    :
  234.                 mark_group_as_read(this);
  235.                 show_groups(&top, this, TRUE);
  236.                 break;
  237.  
  238.             case ' '    :
  239.             case 'n'    :
  240.             case TAB    :
  241.                 tmp_ng = this->next;
  242.                 unread = 0;
  243.                 while (tmp_ng != NULL) {
  244.                     articles = (int) (tmp_ng->hi_num - tmp_ng->lo_num);
  245.                     for (j = 0; j < articles; j++) {
  246.                         if ( *((tmp_ng->read_list)+j) == FALSE)
  247.                             unread++;
  248.                     }
  249.                     if (unread > 0) break;
  250.                     tmp_ng = tmp_ng->next;
  251.                 }
  252.                 if (unread > 0) {
  253.                     this = tmp_ng;
  254.                 } else {
  255.                     message("-- No more articles to read --");
  256.                 }
  257.                 break;
  258.  
  259.             case '!'    :
  260.                 textbackground(BLACK);    textcolor(LIGHTGRAY);
  261.                 cprintf("\r\n");
  262.                 spawnl(P_WAIT, getenv("COMSPEC"), getenv("COMSPEC"), NULL);
  263.                 show_groups(&top, this, TRUE);
  264.                 break;
  265.  
  266.             case ENTER  :
  267.                 exit_code = EX_DONE;
  268.                 break;
  269.  
  270.             case 'q'    :
  271.             case ESCAPE :
  272.                 exit_code = EX_QUIT;
  273.                 break;
  274.         };
  275.         if (exit_code == EX_CONT)
  276.             show_groups(&top, this, FALSE);
  277.     }
  278.  
  279.     if (exit_code == EX_DONE)
  280.         return(this);
  281.     else
  282.         return(NULL);
  283.  
  284. }
  285.  
  286.  
  287.  
  288. /*---------------------------- help screen ----------------------------------*/
  289. void show_help(int h)
  290. {
  291.  
  292.     char *type[] = {"New Group",  "Thread",  "Article"};
  293.  
  294.     textbackground(helpb);    textcolor(helpf);
  295.     clrscr();
  296.     textbackground(headb);    textcolor(headf);
  297.     clreol();
  298.     cprintf("         %s Help  (%s)\r\n", type[h], VERSION);
  299.     clreol();
  300.     textbackground(helpb);    textcolor(helpf);
  301.  
  302.     switch (h) {
  303.         case HELP_GROUP   :
  304.  
  305.     cprintf("\r\n\r\n");
  306.     cprintf("   LIST NEWSGROUPS                     CHOOSING NEWSGROUPS\r\n\r\n");
  307.     cprintf("   PgUp  move display up one page      TAB    go to next unread newsgroup\r\n");
  308.     cprintf("   PgDn  move display down one page    ENTER  read selected newsgroup\r\n");
  309.     cprintf("   Home  move to top of list\r\n");
  310.     cprintf("   End   move to bottom of list\r\n");
  311.     cprintf("   \x18     move up one line\r\n");
  312.     cprintf("   \x19     move down one line\r\n");
  313.     cprintf("   /     search for text in group name\r\n\r\n");
  314.              /* 1                                      2                               */
  315.     cprintf("   POST ARTICLE                        SPECIAL ACTIONS\r\n\r\n");
  316.     cprintf("   p     post article in newsgroup     c      mark this newsgroup as read\r\n");
  317.     cprintf("                                       F2     show user values and info\r\n");
  318.     cprintf("                                       !      shell to DOS (EXIT to return)\r\n");
  319.  
  320.     break;
  321.  
  322.         case HELP_THREAD  :
  323.          /* 1                                   2                                     */
  324.     cprintf("\r\n\r\n");
  325.     cprintf("   LISTING THREADS                     READING THREADS\r\n\r\n");
  326.  
  327.     cprintf("   PgUp  move display up one page      TAB    read next unread article\r\n");
  328.     cprintf("   PgDn  move display down one page    ENTER  read the selected thread\r\n");
  329.     cprintf("   Home  move to top of list           \x1a      read the selected thread\r\n");
  330.     cprintf("   End   move to bottom of list        BACKSP read final article in thread\r\n");
  331.     cprintf("   \x18     move up one line\r\n");
  332.     cprintf("   \x19     move down one line\r\n");
  333.     cprintf("   +     search for text in subject\r\n\r\n");
  334.  
  335.     cprintf("   MISCELLANEOUS ACTIONS               EXTRACT THREADS\r\n\r\n");
  336.     cprintf("   p     post article in newsgroup     s      save thread to disk\r\n");
  337.     cprintf("   c     mark this newsgroup as read   w      append article to extract file\r\n");
  338.     cprintf("   F2    show user values and info\r\n");
  339.     cprintf("   F3    cycle through character sets\r\n");
  340.     cprintf("   !     shell to DOS (EXIT to return)\r\n");
  341.  
  342.     break;
  343.  
  344.         case HELP_ARTICLES:
  345.  
  346.     cprintf("\r\n\r\n");
  347.     cprintf("   READING ARTICLES                      CHOOSING ARTICLES\r\n");
  348.     cprintf("   PgUp  move display up one page        TAB   read next unread article\r\n");
  349.     cprintf("   PgDn  move display down one page      ENTER read next article\r\n");
  350.     cprintf("   Home  move to top of article          \x1a     read next article in thread\r\n");
  351.     cprintf("   End   move to bottom of article       \x1b     read prior article in thread\r\n");
  352.     cprintf("   \x18     move up one line                ctl-\x1d skip to 10th article\r\n");
  353.     cprintf("   \x19     move down one line\r\n");
  354.     cprintf("   /     search for text in article      SEND MAIL\r\n");
  355.     cprintf("                                         r   mail reply to author\r\n");
  356.     cprintf("   POST ARTICLE                          R   mail reply to someone\r\n");
  357.     cprintf("   p   post new article                  m   mail article to someone\r\n");
  358.     cprintf("   f   post follow-up article\r\n");
  359.     cprintf("                                         SPECIAL ACTION\r\n");
  360.     cprintf("   EXTRACT ARTICLES                      x    decode ROT-13 article\r\n");
  361.     cprintf("   s   save article to file              c    mark all articles as read\r\n");
  362.     cprintf("   w   append article to extract file    F2   show user values and info\r\n");
  363.     cprintf("   |   pipe article through command      F3   cycle through character sets\r\n");
  364.     cprintf("   F4  pipe article to hotpipe command   !    shell to DOS (EXIT to return)\r\n");
  365.         break;
  366.     };
  367.  
  368.     message("-- Press any key to continue --");
  369.     get_any_key();
  370.  
  371. }
  372.  
  373. /*-------------- show the values of user defined variables -------------*/
  374. void show_values(void)
  375. {
  376.     textbackground(helpb);    textcolor(helpf);
  377.     clrscr();
  378.     textbackground(headb);    textcolor(headf);
  379.     clreol();
  380.     cprintf("    User values and info (%s)\r\n", VERSION);
  381.     clreol();
  382.     textbackground(helpb);    textcolor(helpf);
  383.     cprintf("\r\n\r\n");
  384.  
  385.     cprintf("   User Name:   %s@%s  (%s)\r\n\r\n",my_stuff.user, my_stuff.my_domain,
  386.                            my_stuff.my_name);
  387.  
  388.     cprintf("   Local node name:    %s\r\n",my_stuff.my_site);
  389.     cprintf("   Mail Server:        %s\r\n",my_stuff.mail_server);
  390.     cprintf("   Organization:       %s\r\n\r\n",my_stuff.my_organisation);
  391.  
  392.     cprintf("   Signature File:     %s\r\n",my_stuff.signature);
  393.     cprintf("   Reply-To:           %s\r\n",my_stuff.replyuser);
  394.     cprintf("   Aliases file:       %s\r\n",my_stuff.alias_file);
  395.     cprintf("   Extract file:       %s\r\n",my_stuff.extract_file);
  396.     cprintf("   File Editor:        %s\r\n",my_stuff.editor);
  397.     cprintf("   F4 hotkey pipe to:  %s\r\n\r\n",my_stuff.hotpipe);
  398.  
  399.     cprintf("   News Home Directory:  %s\r\n",my_stuff.home);
  400.     cprintf("   Temporary Directory:  %s\r\n\r\n",my_stuff.temp_str);
  401.  
  402.     cprintf("   Active code page:     %d%s\r\n", active_code_page,
  403.         (code_page_table == CP_NOT_SUPP) ? " (unsupported)" : "");
  404.     cprintf("   Last article charset: %s\r\n", char_set_names[current_char_set]);
  405.  
  406.     message("-- Press any key to continue --");
  407.     get_any_key();
  408.  
  409. }
  410.  
  411. /*-------------------- show the list of active groups -----------------------*/
  412. void show_groups(ACTIVE **top, ACTIVE *this, int force)
  413. {
  414.     /*
  415.      *  This routine takes 'top', a pointer to the first line on the screen
  416.      *  and 'this' a pointer to where we want to be, and updates the screen.
  417.      *  A maker to this is maintained, and the screen is repainted, where
  418.      *  necessary
  419.      */
  420.  
  421.     static last_y;
  422.     static last_index;
  423.     int    i, ur;
  424.     ACTIVE *that;
  425.  
  426.     /*
  427.      *  If 'this' is above the 'top' or it is more than a screen length below,
  428.      *  or'this and 'top' are both zero, ie first time repaint the screen
  429.      */
  430.     if ( force || ((*top)->index > this->index) || (this->index - (*top)->index) > PAGE_LENGTH-1) {
  431.         if (force) {
  432.             textbackground(textb);    textcolor(textf);
  433.             clrscr();
  434.             textbackground(headb);    textcolor(headf);
  435.             clreol();
  436.             #ifdef __OS2__
  437.             cprintf("         Select Newsgroup  (%s)\r\n", VERSION);
  438.             #else /* __MSDOS__ */
  439.             cprintf("         Select Newsgroup  (%s)  [%ldk]\r\n", VERSION,  farcoreleft()/1024);
  440.             #endif
  441.             clreol();
  442.             cprintf("\r\n");
  443.             clreol();
  444.         }
  445.         textbackground(textb);    textcolor(textf);
  446.  
  447.         /* now adjust the top */
  448.         *top = this;
  449.         for (i = 0; i < (force ? PAGE_LENGTH/2 : last_y); i++) {
  450.             if ((*top)->last == NULL) break;
  451.             *top = (*top)->last;
  452.         }
  453.  
  454.         that = *top;
  455.         for (i = 0; i < PAGE_LENGTH; i++) {
  456.             gotoxy(7, i + PAGE_HEADER);
  457.  
  458.             if (that != NULL) {
  459.                 ur = count_unread_in_group(that);
  460.                 cprintf("%4d. %s", ((*top)->index)+i+1, that->group);
  461.                 if (that->local)
  462.                     cprintf(" (local)");
  463.                 clreol();
  464.                 gotoxy(64, i + PAGE_HEADER);
  465.                 cprintf("%4.0d (%d)", ur, that->hi_num - that->lo_num);
  466.                 that = that->next;
  467.             } else
  468.                 clreol();
  469.         }
  470.  
  471.         gotoxy(5, last_y + PAGE_HEADER);
  472.         last_y     = this->index - (*top)->index;
  473.         last_index = this->index;
  474.  
  475.     } else {
  476.         textbackground(textb);    textcolor(textf);
  477.         gotoxy(5, last_y + PAGE_HEADER);
  478.     }
  479.     putch(' ');        /* erase previous > character */
  480.  
  481.     last_y += (this->index - last_index);
  482.     gotoxy(5, last_y + PAGE_HEADER);
  483.     putch('>');
  484.     last_index = this->index;
  485.  
  486.     command("ESC=quit   TAB=next unread group   ENTER=read group   F1=help");
  487.     textbackground(BLACK);    textcolor(LIGHTGRAY);    /* in case we exit program */
  488. }
  489.  
  490. /*------------------------- search groups for text --------------------------*/
  491. ACTIVE *search_groups(ACTIVE *this)
  492. {
  493.     char search_text[80];
  494.     static char last_text[80] = {'\0'};
  495.  
  496.     message("Search for? ");
  497.     if (*gets(search_text))
  498.         strcpy(last_text, search_text);
  499.     else
  500.         strcpy(search_text, last_text);
  501.  
  502.     if (search_text[0])
  503.         while (this->next) {
  504.            this = this->next;
  505.             if (stristr(this->group, search_text))
  506.                 break;
  507.         }
  508.  
  509.     message("");
  510.     return this;
  511. }
  512.  
  513.  
  514.  
  515.  
  516.  
  517. /*---------------------------- process group --------------------------------*/
  518. int read_group(ACTIVE *gp)
  519. {
  520.     /*
  521.      *  We now have newsgroup.  Access the directory and try to read
  522.      *  the newsgroup articles, extracting the headers.
  523.      */
  524.  
  525.     ARTICLE *start;
  526.  
  527.     if (gp->lo_num < gp->hi_num) {
  528.         textbackground(textb);    textcolor(textf);
  529.         clrscr();
  530.         textbackground(headb);    textcolor(headf);
  531.         clreol();
  532.         #ifdef __OS2__
  533.         cprintf("         Select Thread  (%s)\r\n", VERSION);
  534.         #else /* __MSDOS__ */
  535.         cprintf("         Select Thread  (%s)  [%ldk]\r\n", VERSION, farcoreleft()/1024);
  536.         #endif
  537.         clreol();
  538.         cprintf("Group: %s\r\n", gp->group);
  539.         clreol();
  540.         textbackground(textb);    textcolor(textf);
  541.  
  542.         start = get_headers(gp);
  543.         select_thread(gp, start);
  544.         free_header(start);
  545.     }
  546.  
  547.     return(0);
  548. }
  549.  
  550.  
  551. /*------------------- show the list of available threads --------------------*/
  552. void show_threads(ACTIVE *gp, ARTICLE **top, ARTICLE *this, int force)
  553. {
  554.     /*
  555.      *  This routine takes 'top', a pointer to the first line on the screen
  556.      *  and 'this' a pointer to where we want to be, and updates the screen.
  557.      *  A maker to this is maintained, and the screen is repainted, where
  558.      *  necessary
  559.      */
  560.  
  561.     static last_y;
  562.     static last_index;
  563.     int    i;
  564.     ARTICLE *that;
  565.     int    unread;
  566.  
  567.     /*
  568.      *  If 'this' is above the 'top' or it is more than a screen length below,
  569.      *  or 'this' and 'top' are both zero (i.e. first time), repaint the screen
  570.      */
  571.     if ( force || ((*top)->index > this->index) || (this->index - (*top)->index) > PAGE_LENGTH-1) {
  572.         if (force) {
  573.             textbackground(textb);    textcolor(textf);
  574.             clrscr();
  575.             textbackground(headb);    textcolor(headf);
  576.             clreol();
  577.             #ifdef __OS2__
  578.             cprintf("         Select Thread  (%s)\r\n", VERSION);
  579.             #else /* __MSDOS__ */
  580.             cprintf("         Select Thread  (%s)  [%ldk]\r\n", VERSION, farcoreleft()/1024);
  581.             #endif
  582.             clreol();
  583.             cprintf("Group: %-57s %5d articles\r\n", gp->group, gp->hi_num - gp->lo_num);
  584.             clreol();
  585.             gotoxy(66,3);
  586.             cprintf("%5d unread\r\n", count_unread_in_group(gp));
  587.         }
  588.         textbackground(textb);    textcolor(textf);
  589.  
  590.         /* now adjust the top -- center selected thread when force is true */
  591.         *top = this;
  592.  
  593.         for (i = 0; i < (force ? PAGE_LENGTH/2 : last_y); i++) {
  594.             if ((*top)->last == NULL) break;
  595.             *top = (*top)->last;
  596.         }
  597.  
  598.         that = *top;
  599.         for (i = 0; i < PAGE_LENGTH; i++) {
  600.             gotoxy(4, i + PAGE_HEADER);
  601.             if (that != NULL) {
  602.                 unread = count_unread_in_thread(gp, that),
  603.                 cprintf("%4d.%5.0d %4d %s", ((*top)->index)+i+1, unread, that->num_articles,
  604.                     translate_header(that->header, current_char_set));
  605.                 that = that->next;
  606.             }
  607.             clreol();
  608.         }
  609.  
  610.         gotoxy(3, last_y + PAGE_HEADER);
  611.         last_y     = this->index - (*top)->index;
  612.         last_index = this->index;
  613.  
  614.     } else {
  615.         textbackground(textb);    textcolor(textf);
  616.         gotoxy(3, last_y + PAGE_HEADER);
  617.     }
  618.  
  619.     putch(' ');        /* erase previous > character */
  620.  
  621.     last_y += (this->index - last_index);
  622.     gotoxy(3, last_y + PAGE_HEADER);
  623.     putch('>');
  624.     last_index = this->index;
  625.  
  626.     command("ESC=select group   TAB=next unread   ENTER=next article   F1=help");
  627. }
  628.  
  629.  
  630.  
  631.  
  632. /*-------------------------- find which group to read -----------------------*/
  633. void select_thread(ACTIVE *gp, ARTICLE *head)
  634. {
  635.     /*
  636.      *  Present the list of threads, and allow him to move up and down with
  637.      *  the arrow and PgUp and PgDn keys.  'h' for help, {-/+ for searches}
  638.      */
  639.  
  640.     ARTICLE *top;        /* thread at the top of the page    */
  641.     ARTICLE *this;       /* current thread                   */
  642.     ARTICLE *th;
  643.     ART_ID  *art;
  644.     enum exit_codes exit_code;    /* why we are exiting the loop      */
  645.     char   sub_tmp[80];
  646.  
  647.     int    i, idx, hit;
  648.     int        a_ct;        /* position of current article in thread */
  649.  
  650.     this = head;
  651.  
  652.     top = head;
  653.     exit_code = EX_CONT;
  654.  
  655.     show_threads(gp, &top, this, TRUE);
  656.  
  657.     while (exit_code == EX_CONT) {
  658.  
  659.         switch (get_any_key()) {
  660.  
  661.                     case Fn1    :
  662.                     case '?'    :
  663.                     case 'h'    :
  664.                         show_help(HELP_THREAD);
  665.                         show_threads(gp, &top, this, TRUE);
  666.                         break;
  667.  
  668.                     case Fn2    :
  669.                         show_values();
  670.                         show_threads(gp, &top, this, TRUE);
  671.                         break;
  672.  
  673.                     case Fn3    :
  674.                         if (current_char_set++ == US_ASCII)
  675.                             current_char_set = 0;
  676.                         show_threads(gp, &top, this, TRUE);
  677.                         break;
  678.  
  679.                     case '['    :
  680.                     case UP_ARR :
  681.                         if (this->last != NULL) this = this->last;
  682.                         break;
  683.  
  684.                     case 'n'    :
  685.                     case ']'    :
  686.                     case DN_ARR :
  687.                         if (this->next != NULL) this = this->next;
  688.                         break;
  689.  
  690.                     case '<'    :
  691.                     case PGUP   :
  692.                         for (i = 0; i < PAGE_LENGTH; i++) {
  693.                             if (this->last == NULL) break;
  694.                             this = this->last;
  695.                         }
  696.                         break;
  697.  
  698.                     case '>'    :
  699.                     case PGDN   :
  700.                         for (i = 0; i < PAGE_LENGTH; i++) {
  701.                             if (this->next == NULL) break;
  702.                             this = this->next;
  703.                         }
  704.                         break;
  705.  
  706.                     case RIGHT  :
  707.                         read_thread(gp, this, this->art_num, 1);
  708.                         show_threads(gp, &top, this, TRUE);
  709.                         break;
  710.  
  711.                     case '^'    :
  712.                     case HOME   :
  713.                         top = this = head;
  714.                         show_threads(gp, &top, this, TRUE);
  715.                         break;
  716.  
  717.                     case '$'    :
  718.                     case END    :
  719.                         this = head;
  720.                         while (this->next != NULL)
  721.                             this = this->next;
  722.                         show_threads(gp, &top, this, TRUE);
  723.                         break;
  724.  
  725.             case 's'    :
  726.                 save_thread_to_disk(gp, this, NULL);
  727.                 break;
  728.  
  729.             case 'w'    :
  730.                 save_thread_to_disk(gp, this, my_stuff.extract_file);
  731.                 break;
  732.  
  733.             case 'p'    :
  734.                 strcpy(sub_tmp, "");
  735.                 post(NULL, gp->group, sub_tmp);
  736.                 show_threads(gp, &top, this, TRUE);
  737.                 break;
  738.  
  739.             case '!'    :
  740.                 textbackground(BLACK);    textcolor(LIGHTGRAY);
  741.                 cprintf("\r\n");
  742.                 spawnl(P_WAIT, getenv("COMSPEC"), getenv("COMSPEC"), NULL);
  743.                 show_threads(gp, &top, this, TRUE);
  744.                 break;
  745.  
  746.             case ' '    :
  747.             case TAB    :
  748.                 /*
  749.                  *  Go to the next unread article.  Work through each
  750.                  *  thread, looking at each article to see if it's been
  751.                  *  read
  752.                  */
  753.  
  754.                 /* for each thread */
  755.                 th = this;
  756.                 hit = FALSE;
  757.                 while (th != NULL) {
  758.  
  759.                     art = th->art_num;
  760.                     a_ct = 0;
  761.  
  762.                     /* for each article */
  763.                     while (art != NULL) {
  764.                         idx = (int)(art->id - gp->lo_num - 1);
  765.                         a_ct++;
  766.                         if ( *((gp->read_list)+idx) == FALSE) {
  767.                             hit = TRUE;
  768.                             break;
  769.                         }
  770.                         art = art->next_art;
  771.                     }
  772.                     if (hit) break;
  773.                     th = th->next;
  774.                 }
  775.  
  776.                 if (hit) {
  777.                     this = th;
  778.                     read_thread(gp, this, art, a_ct);
  779.                     show_threads(gp, &top, this, TRUE);
  780.                 } else {
  781.                     message("-- No more articles to read --");
  782.                 }
  783.                 break;
  784.  
  785.             case 'c'    :
  786.                 if (!mark_group_as_read(gp))
  787.                     exit_code = EX_QUIT;        /* quit to newsgroup menu */
  788.                 else
  789.                     show_threads(gp, &top, this, TRUE);
  790.                 break;
  791.  
  792.             case ENTER  :
  793.                 read_thread(gp, this, this->art_num, 1);
  794.                 show_threads(gp, &top, this, TRUE);
  795.                 break;
  796.  
  797.             case BACKSP :
  798.                 art = this->art_num;
  799.                 a_ct = 1;
  800.                 while (art->next_art != NULL) {
  801.                     a_ct++;
  802.                     art = art->next_art;
  803.                 }
  804.                 read_thread(gp, this, art, a_ct);
  805.                 show_threads(gp, &top, this, TRUE);
  806.                 break;
  807.  
  808.             case '/'    :
  809.             case '+'    :
  810.                 this = search_subjects(this);
  811.                 show_threads(gp, &top, this, FALSE);
  812.                 break;
  813.  
  814.             case ESCAPE :
  815.                 exit_code = EX_QUIT;
  816.                 break;
  817.         };
  818.         if (exit_code == EX_CONT)
  819.             show_threads(gp, &top, this, FALSE);
  820.     }
  821. }
  822.  
  823.  
  824.  
  825.  
  826. /*------------------------ search subjects for text -------------------------*/
  827. ARTICLE *search_subjects(ARTICLE *this)
  828. {
  829.     char search_text[80];
  830.     static char last_text[80] = {'\0'};
  831.  
  832.     message("Search for? ");
  833.     if (*gets(search_text))
  834.         strcpy(last_text, search_text);
  835.     else
  836.         strcpy(search_text, last_text);
  837.  
  838.     if (search_text[0])
  839.         while (this->next) {
  840.            this = this->next;
  841.             if (stristr(translate_header(this->header, current_char_set), search_text))
  842.                 break;
  843.         }
  844.  
  845.     message("");
  846.     return this;
  847. }
  848.  
  849.  
  850. /*------------------------ save a thread ------------------------------*/
  851. void save_thread_to_disk(ACTIVE *gp, ARTICLE *this, char *save_name)
  852. {
  853.     ART_ID *id;
  854.     char   *fn;
  855.     TEXT   *tx;
  856.     LINE   *ln;
  857.     int    a_ct;
  858.     char   fnx[81];
  859.     int    ch;
  860.     FILE   *tmp = NULL;
  861.  
  862.     if (save_name == NULL) {                    /* save function    */
  863.         lmessage("Filename? ");
  864.         gets(fnx);
  865.     } else
  866.         strcpy(fnx, save_name);                    /* extract function */
  867.  
  868.     if (!*fnx) {                    /* file name not given */
  869.         message("");
  870.         return;
  871.     }
  872.     /* if file name starts with ~/ change to user's home directory */
  873.     if ((fnx[0] == '~') && ((fnx[1] == '/') || (fnx[1] == '\\')) && fnx[2]) {
  874.         memmove(fnx+strlen(my_stuff.home), fnx+2, strlen(fnx+2) + 1);
  875.         memmove(fnx, my_stuff.home, strlen(my_stuff.home));
  876.     }
  877.  
  878.     if (save_name) {                /* extract function */
  879.        if (access(fnx, 0) == 0) {
  880.           if ((tmp = fopen(fnx, "at")) == NULL) {
  881.              message("*** Cannot open file for appending - "
  882.                      "please any key to continue ***");
  883.              get_any_key();
  884.           }
  885.        }
  886.        else {
  887.           if ((tmp = fopen(fnx, "wt")) == NULL) {
  888.              message("*** Cannot open file for output - press any key to continue ***");
  889.              get_any_key();
  890.           }
  891.        }
  892.     }
  893.     else {                        /* save function */
  894.         if (access(fnx, 0) == 0) {
  895.             message("File exists - append (y/n)? ");
  896.             while (((ch = tolower(getch())) != 'y') && (ch != 'n'));
  897.             if (ch == 'y') {
  898.                 if ((tmp = fopen(fnx, "at")) == NULL) {
  899.                     message("*** Cannot open file for appending - "
  900.                             "please any key to continue ***");
  901.                     get_any_key();
  902.                 }
  903.             }
  904.         }
  905.         else {
  906.             /* make sure the file name given doesn't have bad characters */
  907.             if ((strcspn(fnx, " \"*+,;<=>?[]|\x7f\xe5") != strlen(fnx)) ||
  908.                 ((tmp = fopen(fnx, "wt")) == NULL)) {
  909.                 message("*** Cannot open file for output - press any key to continue ***");
  910.                 get_any_key();
  911.             }
  912.         }
  913.     }
  914.  
  915.     if (tmp != NULL) {
  916.  
  917.         fn = make_news_group_name(gp->group);
  918.  
  919.         id = this->art_num;
  920.         a_ct = 0;
  921.  
  922.         while (id != NULL) {
  923.             if (save_name) {            /* extract function */
  924.                fputs("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\n",tmp);
  925.             }
  926.  
  927.             tx = load_article(fn, id->art_off);
  928.  
  929.             ln = tx->top;
  930.             while (ln != NULL) {
  931.                 fputs(ln->data, tmp);
  932.                 ln = ln->next;
  933.             }
  934.             fputs("\n", tmp);
  935.             a_ct++;
  936.             id = id->next_art;
  937.             free_article(tx);
  938.         }
  939.         fclose(tmp);
  940.     }
  941.  
  942.     message("");
  943. }
  944.  
  945.  
  946.  
  947. /*------------------------ read a thread ------------------------------*/
  948. enum exit_codes read_thread(ACTIVE *gp, ARTICLE *this, ART_ID *first, int a_ct)
  949. {
  950.     ART_ID *id;
  951.     char   *fn;
  952.     int    idx, cnt;
  953.     enum exit_codes res = EX_CONT;
  954.     TEXT   *tx;
  955.     char   author[WHO_LENGTH], msg_id[MSG_ID_LENGTH];
  956.     CROSS_POSTS *h, *h0;
  957.     ACTIVE *gx;
  958.  
  959.  
  960.     fn = make_news_group_name(gp->group);
  961.  
  962.     id = first;
  963.  
  964.     while ((id != NULL) && (res != EX_QUIT)) {
  965.  
  966.         tx = load_article(fn, id->art_off);
  967.  
  968.         res = read_article(gp, tx, this->header, a_ct, this->num_articles);
  969.  
  970.         /* mark this article as read */
  971.         idx = (int) ((id->id) - gp->lo_num - 1);
  972.         *((gp->read_list)+idx) = TRUE;
  973.  
  974.         /* mark the crossposts */
  975.         get_his_stuff(tx, author, msg_id);
  976.         if ((h0 = look_up_history(msg_id, gp->group)) != NULL) {
  977.  
  978.             h = h0;
  979.             while (h != NULL) {
  980.                 gx = find_news_group(h->group);
  981.                 if (gx) {
  982.                     idx = (int) ((h->art_num) - gx->lo_num - 1);
  983.                     *((gx->read_list)+idx) = TRUE;
  984.                 }
  985.                 h = h->next;
  986.             }
  987.  
  988.             free_cross_post_list(h0);
  989.         }
  990.  
  991.         switch (res) {
  992.             case EX_PREVIOUS:        /* go to previous article */
  993.                 a_ct--;
  994.                 id = id->prev_art;
  995.                 break;
  996.  
  997.             case EX_PREVIOUS10:        /* skip past previous 9 articles */
  998.                 for (cnt = 0; (cnt < 10) && id->prev_art; ++cnt) {
  999.                     a_ct--;
  1000.                     id = id->prev_art;
  1001.                 }
  1002.                 break;
  1003.  
  1004.             case EX_NEXT:            /* go to next article */
  1005.                 a_ct++;
  1006.                 id = id->next_art;
  1007.                 break;
  1008.  
  1009.             case EX_NEXT10:            /* skip past next 9 articles */
  1010.                 for (cnt = 0; (cnt < 10) && id->next_art; ++cnt) {
  1011.                     a_ct++;
  1012.                     id = id->next_art;
  1013.                 }
  1014.                 break;
  1015.  
  1016.             case EX_NEXT_UNREAD:
  1017.                 while (id != NULL) {
  1018.                     idx = (int)(id->id - gp->lo_num - 1);
  1019.                     if ( *((gp->read_list)+idx) == FALSE)
  1020.                         break;
  1021.                     a_ct++;
  1022.                     id = id->next_art;
  1023.                 }
  1024.                 if (id == NULL)
  1025.                     message("-- No more articles in thread --");
  1026.                 break;
  1027.  
  1028.         } /* switch */
  1029.  
  1030.         free_article(tx);
  1031.  
  1032.     } /* while */
  1033.  
  1034.     return(res);
  1035. }
  1036.  
  1037.  
  1038. /*------------- compare multipart headers using some intelligence -----------*/
  1039. /* These routines are modified from snews 2.0 (OS/2 version) patches by Kai
  1040.    Uwe Rommel */
  1041. static int strip_off_part(char *str)
  1042. {
  1043.   char *ptr = str + strlen(str);    /* point to terminating null */
  1044.  
  1045.   /* strip from the end of the line (case-insensitive) things like:
  1046.      - "Part01/10"
  1047.      - "Part 01/10"
  1048.      - "Part 01 of 10"
  1049.      - "[1/10]"
  1050.      - "(1 of 10)"
  1051.      - "1 of 10"
  1052.      - "Patch02a/04"
  1053.      - "Patch20"
  1054.      returns the length of the string without the part number section,
  1055.         or 0 if the string doesn't have part numbers
  1056.    */
  1057.  
  1058.     /* kill trailing spaces */
  1059.   while ( ptr > str && ptr[-1] == ' ' )
  1060.     ptr--;
  1061.  
  1062.     /* kill trailing brackets */
  1063.   if ( ptr > str && (ptr[-1] == ')' || ptr[-1] == ']') )
  1064.     ptr--;
  1065.  
  1066.     /* kill second part number */
  1067.   while ( ptr > str && isdigit(ptr[-1]) )
  1068.     ptr--;
  1069.  
  1070.     /* abort if there isn't a part number to the string */
  1071.   if ( !isdigit(*ptr) )
  1072.     return 0;    /* header is not part of a multipart subject */
  1073.  
  1074.     /* kill /, of, Patch */
  1075.   if ( ptr > str && ptr[-1] == '/' )
  1076.     ptr--;
  1077.   else if ( ptr > str + 3 && strnicmp(ptr - 4, " of ", 4) == 0 )
  1078.     ptr -= 4;
  1079.   else if ( ptr > str + 4 && strnicmp(ptr - 5, "Patch", 5) == 0 )
  1080.   {
  1081.     ptr -= 5;
  1082.     goto label;
  1083.   }
  1084.   else if ( ptr > str + 5 && strnicmp(ptr - 6, "Patch ", 6) == 0 )
  1085.   {
  1086.     ptr -= 6;
  1087.     goto label;
  1088.   }
  1089.   else
  1090.     return 0;    /* header is not part of a multipart subject */
  1091.  
  1092.     /* kill Patch version */
  1093.   if ( ptr > str && 'a' <= ptr[-1] && ptr[-1] <= 'z' )
  1094.     ptr--;
  1095.  
  1096.     /* kill first part number */
  1097.   while ( ptr > str && isdigit(ptr[-1]) )
  1098.     ptr--;
  1099.  
  1100.     /* abort if there isn't a part number to the string */
  1101.   if ( !isdigit(*ptr) )
  1102.     return 0;    /* header is not part of a multipart subject */
  1103.  
  1104.     /* kill opening bracket */
  1105.   if ( ptr > str && (ptr[-1] == '(' || ptr[-1] == '[') )
  1106.     ptr--;
  1107.  
  1108.     /* kill spaces before bracket */
  1109.   while ( ptr > str && ptr[-1] == ' ' )
  1110.     ptr--;
  1111.  
  1112.   if ( ptr > str + 3 && strnicmp(ptr - 4, "Part", 4) == 0 )
  1113.     ptr -= 4;
  1114.  
  1115. label:
  1116.   while ( ptr > str && ptr[-1] == ' ' )
  1117.     ptr--;
  1118.  
  1119.   if ( ptr > str && ptr[-1] == ',' )
  1120.     ptr--;
  1121.   else if ( ptr > str && ptr[-1] == ':' )
  1122.     ptr--;
  1123.  
  1124. /*  *ptr = 0;    */        /* truncate the new string */
  1125.  
  1126.   return (int) (ptr-str);    /* return length of string without part numbers */
  1127. }
  1128.  
  1129.  
  1130. /*-------------------- strip off volume and issue numbers -------------------*/
  1131. static char *skip_vi(char *str)
  1132. {
  1133.   char *ptr = str;
  1134.  
  1135.   /* skip things like "v02i0027: "
  1136.      returns pointer to the substring without the volume/issue number */
  1137.  
  1138.   while ( isspace(*ptr) )
  1139.     ptr++;
  1140.  
  1141.   if ( *ptr++ != 'v' )
  1142.     return str;
  1143.  
  1144.   if ( !isdigit(*ptr) )
  1145.     return str;
  1146.  
  1147.   while ( isdigit(*ptr) )
  1148.     ptr++;
  1149.  
  1150.   if ( *ptr++ != 'i' )
  1151.     return str;
  1152.  
  1153.   if ( !isdigit(*ptr) )
  1154.     return str;
  1155.  
  1156.   while ( isdigit(*ptr) )
  1157.     ptr++;
  1158.  
  1159.   if ( *ptr++ != ':' )
  1160.     return str;
  1161.  
  1162.   if ( *ptr++ != ' ' )
  1163.     return str;
  1164.  
  1165.   while ( isspace(*ptr) )
  1166.     ptr++;
  1167.  
  1168.   return ptr;
  1169. }
  1170.  
  1171. /*------------- compare multipart headers using some intelligence -----------*/
  1172. int smartcmp(char *str1, char *str2)
  1173. {
  1174.   int no_part_len1, no_part_len2;
  1175.  
  1176.   /* Returns 0 if str1 and str2 are the same except for Part numbers
  1177.      Returns <> 0 if they aren't */
  1178.  
  1179.   if (((no_part_len1 = strip_off_part(str1)) != 0) &&
  1180.       ((no_part_len2 = strip_off_part(str2)) != 0)) {
  1181.         if (no_part_len1 == no_part_len2) {
  1182.  
  1183.             /* strings are part of multipart subjects */
  1184.             return strnicmp(skip_vi(str1), skip_vi(str2), no_part_len1);
  1185.  
  1186.         } else
  1187.             return 1;
  1188.   }
  1189.  
  1190.   return stricmp(str1, str2);
  1191. }
  1192.  
  1193.  
  1194. /*------------------------- read the headers --------------------------*/
  1195. ARTICLE *get_headers(ACTIVE *gp)
  1196. {
  1197.     /*
  1198.      *  Read the files, get the headers and add them to the linked list
  1199.      */
  1200.  
  1201.     char *fn;
  1202.     char buf[256], fnx[256], *buf_p;
  1203.     long g, n_read;
  1204.     FILE *tmp_file;
  1205.  
  1206.     ARTICLE *start, *that, *tmp;
  1207.     ART_ID  *art_this, *new;
  1208.     int     ct_art;
  1209.  
  1210.     n_read = 0;
  1211.     ct_art = 0;
  1212.     start = NULL;
  1213.  
  1214.     fn = make_news_group_name(gp->group);
  1215.     sprintf(fnx, "%s.IDX", fn);
  1216.  
  1217.     gotoxy(1,25);
  1218.     cprintf("Articles processed: ");
  1219.  
  1220.     if ((tmp_file = flockopen(fnx, "rb")) != NULL) {
  1221.  
  1222.         for (g = gp->lo_num+1; g <= gp->hi_num; g++) {
  1223.  
  1224.             if ((n_read++ % 10) == 0) {
  1225.                 gotoxy(21,25);
  1226.                 cprintf("%d", n_read-1);    /* # articles processed */
  1227.             }
  1228.  
  1229.             /*
  1230.              *  Read the index
  1231.              *  Search the linked list for the subject
  1232.              *    - allocate a new subject if necessary
  1233.              *    - add to the existing list
  1234.              */
  1235.  
  1236.             if (fgets(buf, 255, tmp_file) == NULL) {
  1237.                 gotoxy(1,25);
  1238.                 fprintf(stderr, "\nsnews: index file is corrupt\n");
  1239.                 exit(1);
  1240.             }
  1241.  
  1242.             /* check all is in sync */
  1243.             if (g != atol(buf+9)) {
  1244.                 gotoxy(1,25);
  1245.                 fprintf(stderr, "\nsnews: article %ld found when %ld expected\n", atol(buf+9), g);
  1246.                 exit(1);
  1247.             }
  1248.  
  1249.             /* skip the two eight digit numbers and the 9 and the spaces */
  1250.             buf_p = buf+28;
  1251.  
  1252.             eat_gunk(buf_p);
  1253.             tmp = start;
  1254.             while (tmp != NULL) {
  1255.                 if (smartcmp(buf_p, tmp->header) == 0)
  1256.                     break;
  1257.                 tmp = tmp->next;
  1258.             }
  1259.  
  1260.             if (tmp != NULL) {
  1261.  
  1262.                 /* allocate new article number */
  1263.                 new = xmalloc(sizeof (ART_ID));
  1264.                 new->id = g;
  1265.                 new->art_off = atol(buf);
  1266.                 new->next_art = NULL;
  1267.                 new->prev_art = NULL;
  1268.                 tmp->num_articles++;
  1269.  
  1270.                 /* place it at the end */
  1271.                 art_this = tmp->art_num;
  1272.                 while (art_this->next_art != NULL) {
  1273.                     art_this = art_this->next_art;
  1274.                 }
  1275.  
  1276.                 art_this->next_art = new;
  1277.                 new->prev_art      = art_this;
  1278.             }
  1279.             else {
  1280.  
  1281.                 /* not found - allocate new thread */
  1282.                 if (start == NULL) {
  1283.                     start = that = xmalloc(sizeof (ARTICLE));
  1284.                     start->last = NULL;
  1285.                     start->next = NULL;
  1286.                     start->index = ct_art;
  1287.                 } else {
  1288.                     ct_art++;
  1289.                     that->next = xmalloc(sizeof (ARTICLE));
  1290.                     that->next->last = that;
  1291.                     that = that->next;
  1292.                     that->next = NULL;
  1293.                     that->index = ct_art;
  1294.                 }
  1295.  
  1296.                 /* store article data */
  1297.                 strcpy(that->header, buf_p);
  1298.                 that->num_articles = 1;
  1299.                 that->art_num = xmalloc(sizeof (ART_ID));
  1300.                 that->art_num->next_art = NULL;
  1301.                 that->art_num->prev_art = NULL;
  1302.                 that->art_num->id = g;
  1303.                 that->art_num->art_off = atol(buf);
  1304.  
  1305.             }
  1306.  
  1307.             that->next = NULL;
  1308.  
  1309.         }
  1310.  
  1311.         fclose(tmp_file);
  1312.  
  1313.     }
  1314.     else {
  1315.         gotoxy(1,25);
  1316.         fprintf(stderr, "\nsnews: can't open index file %s\n", fnx);
  1317.         exit(1);
  1318.     }
  1319.  
  1320.     return(start);
  1321. }
  1322.  
  1323.  
  1324.  
  1325. /*------------------------ clean up subject line ----------------------------*/
  1326. void eat_gunk(char *buf)
  1327. {
  1328.     /*
  1329.      *  This routine take the header line, and strips the
  1330.      *  word header word, 'Re:', and any extra blanks, trim to 62 chars
  1331.      */
  1332.  
  1333.     char *p = buf;
  1334.  
  1335.     buf[61] = 0;
  1336.  
  1337.     while (*p && isspace(*p)) p++;
  1338.  
  1339.     while (*p && !strnicmp(p, "re:", 3)) {
  1340.         p += 3;
  1341.         while (*p && isspace(*p)) p++;
  1342.     }
  1343.  
  1344.     if (!p)
  1345.         strcpy(buf, "<no subject>");
  1346.     else
  1347.         strcpy(buf, p);
  1348.  
  1349.     p = buf + strlen(buf) - 1;
  1350.     while (*p && (*p == '\n' || *p == '\t' || *p == '\r'))
  1351.         p--;
  1352.     p++; *p = '\0';
  1353. }
  1354.  
  1355.  
  1356. /*-------------------- release the subject structures ---------------------*/
  1357. void free_header(ARTICLE *start)
  1358. {
  1359.     /*
  1360.      *  Work our way through the subject structure releasing all the
  1361.      *  memory
  1362.      */
  1363.  
  1364.     ARTICLE *a, *t;
  1365.     ART_ID  *u, *v;
  1366.  
  1367.     a = start;
  1368.  
  1369.     while (a != NULL) {
  1370.  
  1371.         u = a->art_num;
  1372.         while (u != NULL) {
  1373.             v = u->next_art;
  1374.             free(u);
  1375.             u = v;
  1376.         }
  1377.  
  1378.         t = a->next;
  1379.         free(a);
  1380.         a = t;
  1381.     }
  1382. }
  1383.  
  1384.  
  1385. /*------------------- count unread articles in a thread --------------------*/
  1386. int count_unread_in_thread(ACTIVE *gp, ARTICLE *a)
  1387. {
  1388.     /*
  1389.      *  Take the thread 'a' for the given group 'gp' and return the number
  1390.      *  of unread articles
  1391.      */
  1392.  
  1393.     ART_ID *id;
  1394.     int    ct, idx;
  1395.  
  1396.     ct = 0;
  1397.     id = a->art_num;
  1398.  
  1399.     if (gp->read_list)
  1400.         while (id != NULL) {
  1401.             idx = (int)(id->id - gp->lo_num - 1);
  1402.             if (*((gp->read_list)+idx) == FALSE)
  1403.                 ct++;
  1404.             id = id->next_art;
  1405.         }
  1406.  
  1407.     return(ct);
  1408. }
  1409.  
  1410.  
  1411.  
  1412. /*------------------- count unread articles in a group --------------------*/
  1413. int count_unread_in_group(ACTIVE *gp)
  1414. {
  1415.     /*
  1416.      *  Take the thread 'a' for the given group 'gp' and return the number
  1417.      *  of unread articles
  1418.      */
  1419.  
  1420.     int articles, ct, i;
  1421.  
  1422.     ct = 0;
  1423.     articles = (int)(gp->hi_num - gp->lo_num);
  1424.  
  1425.     for (i = 0; i < articles; i++) {
  1426.         if (*((gp->read_list)+i) == FALSE)
  1427.             ct++;
  1428.     }
  1429.  
  1430.     return(ct);
  1431. }
  1432.  
  1433.  
  1434.  
  1435. /*------------------ mark all articles in a group as read -----------------*/
  1436. int mark_group_as_read(ACTIVE *gp)
  1437. {
  1438.     /*
  1439.      *  Take the thread 'a' for the given group 'gp' and return the number
  1440.      *  of unread articles
  1441.      *  Return nonzero if user aborts
  1442.      */
  1443.  
  1444.     int articles, i, ch;
  1445.  
  1446.     message("Mark all articles as read (y/n)? ");
  1447.     while (((ch = tolower(getch())) != 'y') && (ch != 'n') && (ch != ESCAPE));
  1448.  
  1449.     if (ch == 'y') {
  1450.  
  1451.         articles = (int)(gp->hi_num - gp->lo_num);
  1452.  
  1453.         for (i = 0; i < articles; i++)
  1454.             *((gp->read_list)+i) = TRUE;
  1455.  
  1456.     } else
  1457.         return 1;
  1458.  
  1459.     return 0;
  1460. }
  1461.  
  1462.  
  1463.  
  1464. /*-------------------------- status message ---------------------------------*/
  1465. void message(char *msg)
  1466. {
  1467.     int x;
  1468.  
  1469.     x = 40 - (strlen(msg)/2);
  1470.  
  1471.     gotoxy(1,24);
  1472.     if (msg[0]) {
  1473.         textbackground(msgb);  textcolor(msgf);
  1474.     } else {                            /* blank line to clear message */
  1475.         textbackground(textb);  textcolor(textf);
  1476.     }
  1477.     clreol();
  1478.     gotoxy(x,24);
  1479.     cprintf("%s", msg);
  1480.     textbackground(textb);    textcolor(textf);
  1481. }
  1482.  
  1483.  
  1484. /*-------------------------- status message ---------------------------------*/
  1485. void lmessage(char *msg)
  1486. {
  1487.     gotoxy(1,24);
  1488.     textbackground(msgb);  textcolor(msgf);
  1489.     clreol();
  1490.     cprintf("%s", msg);
  1491.     textbackground(textb);    textcolor(textf);
  1492. }
  1493.  
  1494.  
  1495. /*-------------------------- status message ---------------------------------*/
  1496. void command(char *msg)
  1497. {
  1498.     int x;
  1499.  
  1500.     x = 40 - (strlen(msg)/2);
  1501.     gotoxy(1,25);
  1502.     textbackground(msgb);  textcolor(msgf);
  1503.     clreol();
  1504.  
  1505.     gotoxy(x,25);
  1506.     cprintf(msg);
  1507.  
  1508.     textbackground(textb);    textcolor(textf);
  1509. }
  1510.  
  1511. /*------------------ get any key including function keys --------------------*/
  1512. int get_any_key(void)
  1513. {
  1514.     int result;
  1515.  
  1516.     if ((result = getch()) == 0)
  1517.         result = getch() << 8;
  1518.     return result;
  1519. }
  1520.  
  1521. /*-------------- search for substring in string ignoring case ---------------*/
  1522. /* modified from str_casefind() in Panagiotis Tsirigotis' strlib library */
  1523. char *stristr(char *str, char *sstr)
  1524. {
  1525.     int ssfc = *sstr++ ;                   /* sub-string first char */
  1526.  
  1527.     if ( ssfc == 0 )
  1528.         return( str ) ;
  1529.  
  1530.     ssfc = tolower( ssfc ) ;
  1531.  
  1532.     while ( *str )
  1533.     {
  1534.         char *current = str ;
  1535.         register int strc = *str++ ;
  1536.         char *sp ;                          /* string pointer */
  1537.         char *ssp ;                         /* sub-string pointer */
  1538.  
  1539.         if ( (strc = tolower(strc)) != ssfc )
  1540.             continue ;
  1541.  
  1542.         for ( sp = str, ssp = sstr ;; sp++, ssp++ )
  1543.         {
  1544.             register int sc = *sp ;             /* string char */
  1545.             register int ssc = *ssp ;           /* substring char */
  1546.  
  1547.             /*
  1548.              * End-of-substring means we got a match
  1549.              */
  1550.             if ( ssc == 0 )
  1551.                 return( current ) ;
  1552.  
  1553.             /*
  1554.              * Convert to lower case if alphanumeric
  1555.              */
  1556.             if ( (sc = tolower(sc)) != (ssc = tolower(ssc)))
  1557.                 break ;
  1558.         }
  1559.     }
  1560.  
  1561.     return( NULL ) ;
  1562. }
  1563.  
  1564. /* end */
  1565.